<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p mv-text="msg"></p>
        <p mv-text="msg"></p>
        <hr>
        <p mv-text="aaa"></p>
        <p mv-text="info"></p>
    </div>
</body>
<script>
    /*   vue响应式原理低配法（简洁版）
    function Mvvm(options){//构建初始化构造函数
        // this.$el=document.getElementById(options.el);//这个获取DOM元素的方法不生效？
        this.$el=document.querySelector(options.el);
        // console.log(this.$el);//此为当前实例对象在页面的占位容器
        this.$data=options.data;
        // console.log(this.$data);//此为当前实例中的data数据仓库变量对象
        for(let key in this.$data){//循环该数据仓库变量
            // console.log(key);//此为数据仓库变量中的所有对应属性名
            let select=`[mv-text=${key}]`;//根据自定义变量命名规则，找到符合的选择器
            // console.log(select);
            let Dom=this.$el.querySelectorAll(select);//找到所有含有符合规定选择器的DOM元素集合
            // console.log(Dom);
            for(let i in Dom){
                Dom[i].textContent=this.$data[key];//将实例中的仓库里变量渲染到页面展示
            }
            Object.defineProperty(this,key,{//对给实例对象中的每个变量进行拦截劫持,换句话来说，此操作将循环得到的变量key属性名添加到实例上作为实例对象上的属性存在,这也解释了为什么Vue实例上并没有data属性
                get(){
                    console.log("get方法开启");
                    return this.$data[key];
                },
                set(nv){
                    console.log("set方法开启");
                    this.$data[key]=nv;//将新取得值替换原有的属性值
                    for(let i in Dom){
                        Dom[i].textContent=this.$data[key];//将新值再重新渲染到容器中对应元素上进行展示
                    }
                }//此乃Vue响应式原理的低配法
            })
        }
    };
    var mv=new Mvvm({//通过构造函数new一个mv对象，并将一个配置对象作为实参传入
        el:"#app",
        data:{
            msg:"msg数据变量",
            info:"info数据变量",
            aaa:"aaa数据变量"
        }
    })
    */


    //Vue响应式原理高配法(难懂版)
    function Watcher(){};//用以渲染页面的构造函数(页面响应中转站)
    Watcher.prototype._initDom=function($el,key){
        let select=`[mv-text=${key}]`;//找到符合有此属性选择器的
        this[key]=$el.querySelectorAll(select);
        // console.log(this[key]);//获取有这些属性选择器的所有元素,并将这些对应的属性名作为属性添加到该this对象上
    }
    Watcher.prototype._initData=function(data){
        for(let key in data){
            this.notify(key,data[key]);//从数据仓库中获取数据后执行该方法以此在页面显示
        }
    }
    Watcher.prototype.notify=function(k,value){
        // console.log(this[k]);//找到关于有该k属性的数组集合，并循环赋值
        for(let i in this[k]){
            this[k][i].textContent=value;//将数据仓库中的数据显示在页面上
        }
    }
    function Mvvm(options){
        this._init(options.el,options.data);//实例初始化
    }
    Mvvm.prototype._init=function(el,data){//原型上添加_init()方法
        this.$el=document.querySelector(el);
        // console.log(this.$el);//获得当前实例对象在页面的占位容器位置
        this._data=data;
        // console.log(this._data);//获得当前实例对象的data数据仓库变量
        this._deeps(this._data);
    }
    Mvvm.prototype._deeps=function(data){//原型上添加_deeps()方法,此方法用于拦截数据，以及及时做出响应的中转
        let watcher=new Watcher();
        for(let key in data){
            watcher._initDom(this.$el,key);
            Object.defineProperty(this,key,{
                get(){
                    console.log("get方法启动");
                    return data[key];
                },
                set(nv){
                    console.log("set方法启动");
                    data[key]=nv;
                    watcher.notify(key,data[key]);//当数据更改值之后，立即执行此方法，实现页面响应
                }
            })
        }
        watcher._initData(data);
    }

    var mv=new Mvvm({//通过构造函数new一个mv对象，并将一个配置对象作为实参传入
        el:"#app",
        data:{
            msg:"msg数据变量",
            info:"info数据变量",
            aaa:"aaa数据变量"
        }
    })
</script>
</html>